-- Request Limitation Module For Dangdang.com
-- Written by Kevin.XU
-- 2016/7/19


-- variables on module level
local math = require "math"
local resty_md5 = require "resty.md5"
local resty_string = require "resty.string"

local ngx_shared = ngx.shared
local ngx_now = ngx.now
local setmetatable = setmetatable
local tonumber = tonumber
local type = type
local assert = assert

local index_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

local counter = 1

local _M = {
    _VERSION = '0.1'
}

-- Extract IP from Nginx object
function _M.extract_ip(ngx)
    local my_ip = ngx.req.get_headers()["X-Real-IP"]
    if my_ip == nil then
        my_ip = ngx.req.get_headers()["x_forwarded_for"]
    end
    if my_ip == nil then
        my_ip = ngx.var.remote_addr
    end
    return my_ip
end


-- generate pseudo guid string  
function _M.guid()
    local seed={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}
    local tb={}
    for i=1,32 do
        table.insert(tb,seed[math.random(1,16)])
    end
    local sid=table.concat(tb)
    local t=os.time()
    return string.format('%s-%s-%s-%s-%s-%x',
        string.sub(sid,1,8),
        string.sub(sid,9,12),
        string.sub(sid,13,16),
        string.sub(sid,17,20),
        string.sub(sid,21,32),
        t
    )
end

function _M.to_binary(integer)
    local remaining = tonumber(integer)
    local bin_bits = ''

    for i = 7, 0, -1 do
        local current_power = math.pow(2, i)

        if remaining >= current_power then
            bin_bits = bin_bits .. '1'
            remaining = remaining - current_power
        else
            bin_bits = bin_bits .. '0'
        end
    end

    return bin_bits
end

function _M.from_binary(bin_bits)
    return tonumber(bin_bits, 2)
end


function _M.to_base64(to_encode)
    local bit_pattern = ''
    local encoded = ''
    local trailing = ''

    for i = 1, string.len(to_encode) do
        bit_pattern = bit_pattern .. _M.to_binary(string.byte(string.sub(to_encode, i, i)))
    end

    -- Check the number of bytes. If it's not evenly divisible by three,
    -- zero-pad the ending & append on the correct number of ``=``s.
    if math.mod(string.len(bit_pattern), 3) == 2 then
        trailing = '=='
        bit_pattern = bit_pattern .. '0000000000000000'
    elseif math.mod(string.len(bit_pattern), 3) == 1 then
        trailing = '='
        bit_pattern = bit_pattern .. '00000000'
    end

    for i = 1, string.len(bit_pattern), 6 do
        local byte = string.sub(bit_pattern, i, i+5)
        local offset = tonumber(_M.from_binary(byte))
        encoded = encoded .. string.sub(index_table, offset+1, offset+1)
    end

    return string.sub(encoded, 1, -1 - string.len(trailing)) .. trailing
end


function _M.from_base64(to_decode)
    local padded = to_decode:gsub("%s", "")
    local unpadded = padded:gsub("=", "")
    local bit_pattern = ''
    local decoded = ''

    for i = 1, string.len(unpadded) do
        local char = string.sub(to_decode, i, i)
        local offset, _ = string.find(index_table, char)
        if offset == nil then
             error("Invalid character '" .. char .. "' found.")
        end

        bit_pattern = bit_pattern .. string.sub(_M.to_binary(offset-1), 3)
    end

    for i = 1, string.len(bit_pattern), 8 do
        local byte = string.sub(bit_pattern, i, i+7)
        decoded = decoded .. string.char(_M.from_binary(byte))
    end

    local padding_length = padded:len()-unpadded:len()

    if (padding_length == 1 or padding_length == 2) then
        decoded = decoded:sub(1,-2)
    end
    return decoded
end


function _M.md5(str)
    if not str then
        return nil
    end
    local md = resty_md5:new()
    if not md then
        ngx.log(ngx.ERR, "md5 init failed")
        return nil
    end
    md:update(str)
    local tkey = md:final()
    if not tkey then
        ngx.log(ngx.ERR, "md5 encrypt failed")
        return nil
    end
    return resty_string.to_hex(tkey)
end

return _M


